iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 13
0

0. 前言

經過上篇「Day 12: 了解Trigger Module的神秘面紗(上)~~!」的簡介,相信對Trigger Module的功能及相關Register應該有個簡單的了解了吧!?

今天呢! 為了要準備去跨年,所以我們講點輕鬆的~~!
來看看Debugger如何使用這個Trigger Module當作"Breakpoint"和"Watchpoint"來使用!
  
[2018/01/06] 修改初始化流程
  

1. 課前回顧

上篇「Day 12: 了解Trigger Module的神秘面紗(上)~~!」有提到幾個Trigger Module常用的Registers,這邊簡單的回顧一下

  • 0x7a0 tselect: 用來標明目前正在使用的Trigger Module編號
  • 0x7a1 tdata1: 依照type,可以判斷這個Trigger Module所提供的功能,分成以下兩種
    • mcontrol: 就是一般常用到的"Breakpoint"和"Watchpoint"
    • icount: CPU的計步器XD!
  • 0x7a2 tdata2: 就是放Data的地方!
      
      
      

2. Implementation

終於要來講實作的部分了!

2.1 初始化流程

由於每個Trigger都有可能支援各種不同的功能,因此建議初始化的流程如下:

  1. 對$tselect寫入目前選擇Trigger的編號
  2. 重新讀回$tselect,確認該Trigger是否存在
  3. 讀取$tdata1,確認Trigger支援的功能
  4. 重複整個流程,直到所有Trigger都被檢驗過

參考以下程式碼(src/target/riscv/riscv.c)

int riscv_enumerate_triggers(struct target *target)
{
    RISCV_INFO(r);

    for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;

        riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid,
                GDB_REGNO_TSELECT);

        for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
            r->trigger_count[hartid] = t;

            ///譯註: Step 1. 對$tselect寫入目前選擇Trigger的編號
            riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);
            uint64_t tselect_rb = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TSELECT);
            /* Mask off the top bit, which is used as tdrmode in old
             * implementations. */
            tselect_rb &= ~(1ULL << (riscv_xlen(target)-1));
            
            ///譯註: Step 2. 重新讀回$tselect,確認該Trigger是否存在
            if (tselect_rb != t)
                break;

            ///譯註: Step 3. 讀取$tdata1,確認Trigger支援的功能
            uint64_t tdata1 = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TDATA1);
            int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
            switch (type) {
                case 1:
                    /* On these older cores we don't support software using
                     * triggers. */
                    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
                    break;
                case 2:
                    if (tdata1 & MCONTROL_DMODE(riscv_xlen(target)))
                        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
                    break;
            }
        }

        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);

        LOG_INFO("[%d] Found %d triggers", hartid, r->trigger_count[hartid]);
    }

    return ERROR_OK;
}

  
  

2.2 新增Breakpoint

主要分成三部分(src/target/riscv/riscv.c)
首先是上半部,當GDB傳Breakpoint的資料進來時

    if (breakpoint->type == BKPT_SOFT) {
        
        ....Software Breakpoint的部分省略,待日後說明~~!

    } else if (breakpoint->type == BKPT_HARD) {
        struct trigger trigger;
        trigger_from_breakpoint(&trigger, breakpoint);  ///譯註: 先準備好相關資料
        int result = add_trigger(target, &trigger);  ///譯註: 新增Trigger
        if (result != ERROR_OK)
            return result;

    } else {
        LOG_INFO("OpenOCD only supports hardware and software breakpoints.");
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    breakpoint->set = true;

    return ERROR_OK;

再來看一下中間資料準備的過程

static void trigger_from_breakpoint(struct trigger *trigger,
        const struct breakpoint *breakpoint)
{
    trigger->address = breakpoint->address;
    trigger->length = breakpoint->length;
    trigger->mask = ~0LL;
    trigger->read = false;
    trigger->write = false;
    trigger->execute = true;    ///譯註: 這行很重要,用來標示這是個Breakpoint
    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = breakpoint->unique_id;
}

最後是新增Trigger的部分,基本上就是把準備好的資料,一個個複製到對應的欄位中!

static int maybe_add_trigger_t2(struct target *target, unsigned hartid,
        struct trigger *trigger, uint64_t tdata1)
{
    RISCV_INFO(r);

    /* tselect is already set */
    if (tdata1 & (MCONTROL_EXECUTE | MCONTROL_STORE | MCONTROL_LOAD)) {
        /* Trigger is already in use, presumably by user code. */
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    /* address/data match trigger */
    tdata1 |= MCONTROL_DMODE(riscv_xlen(target));
    tdata1 = set_field(tdata1, MCONTROL_ACTION,
            MCONTROL_ACTION_DEBUG_MODE);
    tdata1 = set_field(tdata1, MCONTROL_MATCH, MCONTROL_MATCH_EQUAL);
    tdata1 |= MCONTROL_M;
    if (r->misa & (1 << ('H' - 'A')))
        tdata1 |= MCONTROL_H;
    if (r->misa & (1 << ('S' - 'A')))
        tdata1 |= MCONTROL_S;
    if (r->misa & (1 << ('U' - 'A')))
        tdata1 |= MCONTROL_U;

    if (trigger->execute)
        tdata1 |= MCONTROL_EXECUTE;
    if (trigger->read)
        tdata1 |= MCONTROL_LOAD;
    if (trigger->write)
        tdata1 |= MCONTROL_STORE;

    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, tdata1);

    uint64_t tdata1_rb = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TDATA1);
    LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);

    if (tdata1 != tdata1_rb) {
        LOG_DEBUG("Trigger doesn't support what we need; After writing 0x%"
                PRIx64 " to tdata1 it contains 0x%" PRIx64,
                tdata1, tdata1_rb);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA2, trigger->address);

    return ERROR_OK;
}

  
  

2.3 新增Watchpoint

一樣主要分成三部分(src/target/riscv/riscv.c)
首先是上半部,當GDB傳Watchpoint的資料進來時

int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint)
{
    struct trigger trigger;
    trigger_from_watchpoint(&trigger, watchpoint);  ///譯註: 先準備好相關資料

    int result = add_trigger(target, &trigger);     ///譯註: 新增Trigger
    if (result != ERROR_OK)
        return result;
    watchpoint->set = true;

    return ERROR_OK;
}

在過來看一下資料準備的部分

static void trigger_from_watchpoint(struct trigger *trigger,
        const struct watchpoint *watchpoint)
{
    trigger->address = watchpoint->address;
    trigger->length = watchpoint->length;
    trigger->mask = watchpoint->mask;
    trigger->value = watchpoint->value;
    trigger->read = (watchpoint->rw == WPT_READ || watchpoint->rw == WPT_ACCESS);   ///譯註: 這行很重要,用來標示這是個Watchpoint,在資料被讀取時比較
    trigger->write = (watchpoint->rw == WPT_WRITE || watchpoint->rw == WPT_ACCESS); ///譯註: 這行很重要,用來標示這是個Watchpoint,在資料被寫入時比較
    trigger->execute = false;
    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = watchpoint->unique_id;
}

最後新增Trigger的部分同上面"# 2.2 新增Breakpoint",不多加贅述

2.4 移除 Trigger

由於Breakpoint和Watchpoint在移除Trigger的方式相同,這邊就統一一起講解比較快XD!
不囉唆,先上程式碼(src/target/riscv/riscv.c)

static int remove_trigger(struct target *target, struct trigger *trigger)
{
    RISCV_INFO(r);

    int first_hart = -1;
    for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        if (first_hart < 0) {
            first_hart = hartid;
            break;
        }
    }
    assert(first_hart >= 0);

    unsigned int i;
    for (i = 0; i < r->trigger_count[first_hart]; i++) {

        ///譯註: 找到之前對應的那個Trigger的編號
        if (r->trigger_unique_id[i] == trigger->unique_id)
            break;
    }
    if (i >= r->trigger_count[first_hart]) {
        LOG_ERROR("Couldn't find the hardware resources used by hardware "
                "trigger.");
        return ERROR_FAIL;
    }
    LOG_DEBUG("Stop using resource %d for bp %d", i, trigger->unique_id);
    for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TSELECT);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);

        ///譯註: 將Data全寫成0,這樣這個Trigger就被關閉了!
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
    }
    r->trigger_unique_id[i] = -1;

    return ERROR_OK;
}

幾本上就是在OpenOCD中,對於每個Breakpoint和Watchpoint都會給他一個unique_id,利用比較這個unique_id就可以快速找到這個Breakpoint/Watchpoint所對應的Trigger編號!

找到該編號用就利用以下步驟關閉他:

  1. 在$tselect中寫入編號(這邊就不用檢查該編號的Trigger存不存在的問題XD)
  2. 將$tdata1寫入0,清掉所有的欄位,這樣這個Trigger就算是被關閉!
      
      
      

99. 結語

大概說明完整個Debug System內容的80%了,整體架構和功能也都有做個淺出的介紹,
詳細內容還是需要對照整份文件和程式碼來看會比較清楚。
尤其是官方三不五時就在改Coding的方式 (翻桌

突然發覺是不是應該拆成3篇,這樣.....就可以安心去跨年了!!
  
  
  

參考資料

  1. RISC-V External Debug Support 0.13
  2. GitHub: riscv/riscv-openocd

上一篇
Day 12: 了解Trigger Module的神秘面紗(上)~~!
下一篇
Day 14: 讓百萬人都驚呆的Debug Transport Module~~(上)
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
achen002
iT邦新手 5 級 ‧ 2022-03-21 15:31:36

Hi HelloWorld,
您好,我是初次接觸RISC-V的新手, 您這系列的文章實在是讓像我這樣的初學者獲益良多,感謝無私分享!

不過在您的文章中的srouce code,在我的理解上是著重在對於OpenOCD的介紹,與說明它如何下發指令來透過JTAG與RISC-V IC裡的debug module溝通,而IC中debug module, debug transprot module, debug module interface...等,則是另外IC設計的範疇,當然還是須配合RISC-V External Debug Support文件來實做。
不知道我這樣的理解是否正確,不過這系列文章已有些時日了,如過您有看到還是希望您能幫我解答一下。感謝!

可以這樣理解沒有錯!
這系列文章主要是以軟體的角度,透過JTAG來操作底層的硬體,
沒有涉入IC設計的範圍!

achen002 iT邦新手 5 級 ‧ 2022-03-23 16:42:43 檢舉

謝謝您的回覆,這樣就解答我的疑惑了。
這系列文章真的寫得很不錯,很適合剛接觸這個領域的人去了解RISC-V的debugging!

我要留言

立即登入留言